iT邦幫忙

2024 iThome 鐵人賽

DAY 5
0
Software Development

螃蟹幼幼班:Rust 入門指南系列 第 5

Day5 - 型別:浮點數以及數值運算

  • 分享至 

  • xImage
  •  

浮點數型別

Rust 針對浮點數有兩種型別f32 和 f64,分別佔有 32 位元與 64 位元的大小。
所有的浮點數型別第一個位元都是用來記錄正負號,不像整數有沒正負號的型別。
浮點數是依照 IEEE-754 所定義的,f32 型別對應單精度浮點數(經度約小數點後6位),而 f64 是雙精度浮點數(經度約小數點後15位)。
因為精確度、和其他語言的兼容性考量,以及現代處理器對於f64運算有做優化所以也不會有明顯的性能損失, Rust 預設的浮點數型別為f64

數值運算

Rust 支援所有想得到的數值型別基本運算:加法、減法、乘法、除法和取餘。

整數運算

整數除法會取最接近零的下界數值。
以下舉例,另外寫一個函數來把結果的 type 印出來,VSCode 的 extension rust-analyzer 也會直接提示型別,一般情況強型別語言因為型別都定義好,也用不到這種函數就是。

首先看整數,基本上整數沒特別指定,Rust 會用預設i32

use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("Type is: {}", type_name::<T>());
}

fn main() {
    // 加法
    let sun = 1 + 2;
    println!("{}", sun); // 3
    print_type_of(&sun); // Type is: i32

    // 減法
    let difference = 5 - 10;
    println!("{}", difference); // -5
    print_type_of(&difference); // Type is: i32

    // 乘法
    let product = 4 * 30;
    println!("{}", product); // 120
    print_type_of(&product); // Type is: i32

    // 除法
    let quotient = 5 / 2;
    println!("{}", quotient); // 2
    print_type_of(&quotient); // Type is: i32
    
    let negative_quotient = -5 / 2;
    println!("{}", negative_quotient); // -2
    print_type_of(&negative_quotient); // Type is: i32

    // 取餘
    let remainder = -8 % 5;
    println!("{}", remainder); // -3
    print_type_of(&remainder); // Type is: i32
}

比較特別是整數除法會向 0 取整,型別不會改變

然後我想說看能不能整數和小數相加,畢竟 JavaScript 可以XD

fn main() {
    let sum = 1.1 + 10;
    println!("{}", sum);
}

結果編譯階段就會報錯了,一定要同類型的數字才能做運算

$ cargo run
error[E0277]: cannot add an integer to a float
 --> src/main.rs:8:19
  |
8 |     let sum = 1.1 + 10;
  |                   ^ no implementation for `{float} + {integer}`
  |
  = help: the trait `Add<{integer}>` is not implemented for `{float}`
  = help: the following other types implement trait `Add<Rhs>`:
            <&'a f128 as Add<f128>>
            <&'a f16 as Add<f16>>
            <&'a f32 as Add<f32>>
            <&'a f64 as Add<f64>>
            <&'a i128 as Add<i128>>
            <&'a i16 as Add<i16>>
            <&'a i32 as Add<i32>>
            <&'a i64 as Add<i64>>
          and 56 others

For more information about this error, try `rustc --explain E0277`.
error: could not compile `types_float` (bin "types_float") due to 1 previous error

10 加上小數點就可以,或是用as關鍵字做型別轉換。

fn main() {
    let sum = 1.1 + 10 as f64;
    println!("{}", sum); // 1.1
}

小數運算

接著看一下有小數情況的運算:

use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("Type is: {}", type_name::<T>());
}

fn main() {
    // 加法
    let sun = 0.1 + 0.2;
    println!("{}", sun); // 0.30000000000000004
    print_type_of(&sun); // Type is: f64

    // 減法
    let difference = 5.5 - 10.387;
    println!("{}", difference); // -4.8870000000000005
    print_type_of(&difference); // Type is: f64

    // 乘法
    let product = 4.7 * 30.2;
    println!("{}", product); // 141.94
    print_type_of(&product); // Type is: f64

    // 除法
    let quotient = 5.2 / 2.1;
    println!("{}", quotient); // 2.4761904761904763
    print_type_of(&quotient); // Type is: f64

    let negative_quotient = -5.2 / 2.1;
    println!("{}", negative_quotient); // -2.4761904761904763
    print_type_of(&negative_quotient); // Type is: f64

    // 取餘
    let remainder = -8.5 % 5.72;
    println!("{}", remainder); // -2.7800000000000002
    print_type_of(&remainder); // Type is: f64
}

不意外有浮點數誤差,運算的結果也還是 f64,除法的部分當然也不會像整數那樣把結果取整了。
經過測試可以知道 Rust 很重要的特性:類型轉換要是顯式的,Rust不會自己轉換型別

NaN及Infinity

如果運算超過範圍,浮點數也會有溢位,會產生 +Infinity(inf)-Infinity(-inf),分別代表正無窮大和負無窮大:

fn main() {
    let positive_infinity = 1.0 / 0.0;
    println!("1.0 / 0.0 = {}", positive_infinity); // inf
    print_type_of(&positive_infinity); // Type is: f64

    let negative_infinity = -1.0 / 0.0;
    println!("-1.0 / 0.0 = {}", negative_infinity); // -inf
    print_type_of(&negative_infinity); // Type is: f64

    let large_number = 1e308 * 1e308; // 超過了 f64 的表示範圍
    println!("1e308 * 1e308 = {}", large_number); // inf
    print_type_of(&large_number); // Type is: f64
}

可以看到型別還是 f64,也可以和其他f64做運算,不過結果不那麼直觀:

fn main() {
    let positive_infinity = f64::INFINITY;
    let negative_infinity = f64::NEG_INFINITY;

    println!("positive_infinity + 10 = {}", positive_infinity + 10.0); // inf
    println!("positive_infinity * -2 = {}", positive_infinity * -2.0); // -inf
    println!("10 / positive_infinity = {}", 10.0 / positive_infinity); // 0
    println!("positive_infinity / positive_infinity = {}", positive_infinity / positive_infinity); // NaN
}

NaN(Not a Number)代表計算結果無意義或未定義。NaN不等於任何值,甚至不等於自身:

fn main() {
    assert_eq!(f64::NAN, f64::NAN);
}
assertion `left == right` failed
  left: NaN
 right: NaN

除了 Infinity 互除以外,0 互除的結果也可能 NaN,不過要注意型別是什麼。

如果 0 是整數的型別編譯會沒辦法通過,而且如果是在 run time 遇到這個情況會讓程式崩潰,所以在 Rust 中進行整數除法時,最好檢查除數是否為 0。

fn main() {
    let nan = 0_i32 / 0_i32;
    println!("{}", nan);
}
$ cargo run
error: this operation will panic at runtime
  --> src/main.rs:47:15
   |
47 |     let nan = 0_i32 / 0_i32;
   |               ^^^^^^^^^^^^^ attempt to divide `0_i32` by zero
   |
   = note: `#[deny(unconditional_panic)]` on by default

error: could not compile `types_float` (bin "types_float") due to 1 previous error

如果是浮點數的話結果會是 NaN,不會報錯,和Infinity一樣型別都會是原本的浮點數型別(f64f32),不同型別不論是什麼都沒辦法做運算。另外NaN和其他數字處理結果都會是NaN

fn main() {
    let positive_infinity = f32::INFINITY;
    let nan = 0_f32 / 0_f32;
    println!("nan: {}", nan);
    println!("nan + 1.0 = {}", nan + 1.0);
    println!("nan + positive_infinity = {}", nan + positive_infinity);
}
$ cargo run
nan: NaN
nan + 1.0 = NaN
nan + positive_infinity = NaN

Rust 有對應的方法來檢查這兩種情況:

fn main() {
    let x: f64 = 1.0 / 0.0;
    let y: f64 = 0.0 / 0.0;

    if x.is_infinite() {
        println!("x is infinite: {}", x);
    }

    if y.is_nan() {
        println!("y is NaN: {}", y);
    }
}

這邊比較特別的是變數一定要指定型別才能用這兩種方法,不然會有以下錯誤訊息,原因是沒有指定的話 Rust 無法確定是f32還是f64的方法。

error[E0689]: can't call method `is_infinite` on ambiguous numeric type `{float}`

結語

今天在寫的時候其實很多地方想要故意寫錯,例如不同型別運算等,但 Rust 的編譯器都會報錯,和 JavaScript 比起來非常繁瑣,不過滿準的型別推斷和清楚的錯誤訊息緩和滿多在修正編譯錯誤時的開發體驗。
另外和 JavaScript 滿大的差異是明確把整數和小數的運算區隔開了,加上型別需要顯示轉換,除了能讓開發者知道每一步驟的狀態和行為,也能減少運算中的不確定性,避免潛在錯誤,也是 Rust 安全性優勢之一。


上一篇
Day4 - 型別:整數
下一篇
Day6 - 型別:字元、布林值
系列文
螃蟹幼幼班:Rust 入門指南25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言